package org.ctstudio.common.utils;

public class ByteUtil {
  private static final ByteUtil _instance = new ByteUtil();

  public static ByteUtil getInstance() {
    return _instance;
  }

  public int hexChar2Int(final char c) {
    int digit;
    if (c >= '0' && c <= '9') {
      digit = c - '0';
    } else if (c >= 'A' && c <= 'F') {
      digit = c - 'A' + 10;
    } else if (c >= 'a' && c <= 'f') {
      digit = c - 'a' + 10;
    } else {
      throw new NumberFormatException("Character must be 'A-F, a-f' or 0-9");
    }
    return digit;
  }

  public byte hexChar2Byte(final char c) {
    return (byte) hexChar2Int(c);
  }

  public byte hex2Byte(final String hex) {
    if (hex.length() > 2)
      throw new IllegalArgumentException("Hex string length must no more than 2.");

    int total = 0;
    for (char c : hex.toCharArray()) {
      total = total << 4 | hexChar2Int(c);
    }
    return (byte) total;
  }

  public byte[] hex2Bytes(String hex) {
    int odd = hex.length() % 2;
    byte[] result = new byte[hex.length() / 2 + odd];

    for (int i = 0; i < hex.length(); i++) {
      char c = hex.charAt(i);
      int resultIndex = (i + odd) / 2;
      result[resultIndex] = (byte) (result[resultIndex] << 4 | hexChar2Int(c));
    }
    return result;
  }

  public byte[] hex2ReverseBytes(String hex) {
    int odd = hex.length() % 2;
    byte[] result = new byte[hex.length() / 2 + odd];

    for (int i = 0; i < hex.length(); i++) {
      char c = hex.charAt(i);
      int resultIndex = result.length - 1 - (i + odd) / 2;
      result[resultIndex] = (byte) (result[resultIndex] << 4 | hexChar2Int(c));
    }
    return result;
  }

  public byte[] fillToLength(byte[] bytes, int length) {
    if (bytes.length >= length)
      return bytes;
    byte[] newBytes = new byte[length];
    System.arraycopy(bytes, 0, newBytes, length - bytes.length, bytes.length);

    return newBytes;
  }

  public byte[] trimToLength(byte[] bytes, int length) {
    if (bytes.length <= length)
      return bytes;
    byte[] newBytes = new byte[length];
    System.arraycopy(bytes, bytes.length - length, newBytes, 0, length);
    return newBytes;
  }

  public byte[] fillOrTrimToLength(byte[] bytes, int length) {
    return fillToLength(trimToLength(bytes, length), length);
  }

  protected byte[] number2Bytes(int typeSize, long o) {
    byte[] result = new byte[typeSize / Byte.SIZE];
    for (int i = result.length - 1; i >= 0; i--) {
      result[i] = (byte) (o & 0xFF);
      o >>>= 8;
    }
    return result;
  }

  protected byte[] number2ReverseBytes(int typeSize, long o) {
    byte[] result = new byte[typeSize / Byte.SIZE];
    for (int i = 0; i < result.length; i++) {
      result[i] = (byte) (o & 0xFF);
      o >>>= 8;
    }
    return result;
  }

  public byte[] short2Bytes(final short src) {
    return number2Bytes(Short.SIZE, src);
  }

  public byte[] short2ReverseBytes(final short src) {
    return number2ReverseBytes(Short.SIZE, src);
  }

  public short bytes2Short(byte[] src, int pos, int len) {
    return (short) bytes2Long(src, pos, len);
  }

  public short reverseBytes2Short(byte[] src, int pos, int len) {
    return (short) reverseBytes2Long(src, pos, len);
  }

  public short bytes2Short(final byte[] src) {
    return (short) bytes2Long(src);
  }

  public short reverseBytes2Short(final byte[] src) {
    return (short) reverseBytes2Short(src);
  }

  public byte[] int2Bytes(final int src) {
    return number2Bytes(Integer.SIZE, src);
  }

  public byte[] int2ReverseBytes(final int src) {
    return number2ReverseBytes(Integer.SIZE, src);
  }

  public int byte2Int(byte src) {
    return src & 0xFF;
  }

  public int bytes2Int(byte[] src, int pos, int len) {
    return (int) bytes2Long(src, pos, len);
  }

  public int bytes2Int(byte[] src) {
    return (int) bytes2Long(src);
  }

  public int reverseBytes2Int(byte[] src, int pos, int len) {
    return (int) reverseBytes2Long(src, pos, len);
  }

  public int reverseBytes2Int(byte[] src) {
    return (int) reverseBytes2Long(src);
  }

  public byte[] long2Bytes(final long src) {
    return number2Bytes(Long.SIZE, src);
  }

  public byte[] long2ReverseBytes(final long src) {
    return number2ReverseBytes(Long.SIZE, src);
  }

  public long bytes2Long(byte[] src, int pos, int len) {
    long result = 0;
    if (len <= 0) {
      return result;
    }
    int ubound = Math.min(src.length, pos + len);
    for (int i = pos; i < ubound; i++) {
      result <<= 8;
      result |= src[i] & 0xFF;
    }
    return result;
  }

  public long bytes2Long(byte[] src) {
    return bytes2Long(src, 0, src.length);
  }

  public long reverseBytes2Long(byte[] src, int pos, int len) {
    long result = 0;
    if (len <= 0) {
      return result;
    }
    int ubound = Math.min(src.length, pos + len);
    for (int i = ubound - 1; i >= pos; i--) {
      result <<= 8;
      result |= src[i] & 0xFF;
    }
    return result;
  }

  public long reverseBytes2Long(byte[] src) {
    return reverseBytes2Long(src, 0, src.length);
  }

  public byte[] join(byte[]... bs) {
    byte[] result;
    int totalLength = 0;
    for (byte[] ba : bs) {
      if (ba != null) {
        totalLength += ba.length;
      }
    }
    result = new byte[totalLength];
    int destPos = 0;
    for (byte[] ba : bs) {
      if (ba != null) {
        System.arraycopy(ba, 0, result, destPos, ba.length);
        destPos += ba.length;
      }
    }
    return result;
  }

  private static final char[] HEX_CHAR_LOWERCASE = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
  private static final char[] HEX_CHAR_UPPERCASE = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };

  private String byteToHexString(char[] hexCharArray, byte b) {
    return new String(new char[] { hexCharArray[(b & 0xF0) >> 4], hexCharArray[b & 0xF] });
  }

  public String byte2HexLowercase(byte b) {
    return byteToHexString(HEX_CHAR_LOWERCASE, b);
  }

  public String byte2HexUppercase(byte b) {
    return byteToHexString(HEX_CHAR_UPPERCASE, b);
  }

  private String bytesToHexString(char[] hexCharArray, byte[] bytes) {
    StringBuilder sb = new StringBuilder(bytes.length * 2);
    for (byte b : bytes) {
      sb.append(hexCharArray[(b & 0xF0) >>> 4]).append(hexCharArray[b & 0xF]);
    }
    return sb.toString();
  }

  public String bytes2HexLowercase(byte[] bytes) {
    return bytesToHexString(HEX_CHAR_LOWERCASE, bytes);
  }

  public String bytes2HexUppercase(byte[] bytes) {
    return bytesToHexString(HEX_CHAR_UPPERCASE, bytes);
  }

  public void reverse(byte[] bs) {
    byte b;
    for (int i = 0; i < bs.length / 2; i++) {
      b = bs[i];
      bs[i] = bs[bs.length - i - 1];
      bs[bs.length - i - 1] = b;
    }
  }
}
